﻿using System;

using jade.content;
using jade.content.onto;
using jade.core;
using jade.domain;
using jade.domain.FIPAAgentManagement;
using jade.proto;
using log4net;
using GatewayUtil.gatewayConfig;
using Iterator = jade.util.leap.Iterator;
using Hashtable = System.Collections.Hashtable;
using ACLMessage = jade.lang.acl.ACLMessage;
using Ontology2WSDL;
using jade.domain.introspection;
using System.IO;
using WSInspection;



namespace GatewayUtil.agents
{
    /// <summary>
    /// Agent is responsible for handling information messages includes registering/deregistering an agents.
    /// </summary>
    public class SubscriptionAgent : Agent
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(SubscriptionAgent));
        public static AID defaultDF = null;
        public static SubscriptionAgent _instance = null;
        public static Hashtable wsStore = null;
        public SubscriptionAgent()
        {
            _instance = this;
        }
        public override void setup()
        {
            log.Debug(this.getLocalName() + ": Starting agent ");

            // Subscribe to the DF
            DFAgentDescription template = new DFAgentDescription();
            ServiceDescription sd = new ServiceDescription();
            
            // use the code below if you want to receive message only for agent that has a flag PUBLIC_AS_WS = true          
            //sd.addProperties(new Property(GWConst.PUBLIC_AS_WEBSERVICE, "true"));            
            template.addServices(sd);

            ACLMessage subscriptionMsg = DFService.createSubscriptionMessage(this, getDefaultDF(), template, null);
            
            wsStore = (Hashtable)getArguments()[0];
            string urlLocation = (string)getArguments()[1];
            string path = (string) getArguments()[2];

            addBehaviour(new SubscriptionBehaviour(this, subscriptionMsg, wsStore, urlLocation, path));

            addBehaviour( new OwnAMSSubscriber());
            
            
        }

        protected void debug(string p)
        {
            log.Debug(this.getLocalName() + ": " + p);

        }
        protected void error(string p)
        {
            log.Error(this.getLocalName() + ": " + p);
        }

        protected void error(Exception e)
        {
            log.Error(e);
        }
    
        class OwnAMSSubscriber:AMSSubscriber{



            public  override void installHandlers(java.util.Map handlersTable) {
					handlersTable.put("dead-agent", new OwnEventHandler());
            }
            class OwnEventHandler:EventHandler
            {


                #region EventHandler Members
                public void handle(Event ev) {
							DeadAgent da = (DeadAgent)ev;
							AID id = da.getAgent();
							// Deregister the dead agent in case it was registered
							try {
								DFAgentDescription dfd = new DFAgentDescription();
								dfd.setName(id);
                                //DFService.deregister(da.getAgent());
                                SubscriptionAgent.wsStore.Remove(id.getName().Split('@')[0]);                                
                                 
							}
							catch (Exception e) {
								// Just do nothing
							}
						}
                #endregion

                        void DFDeregister(DFAgentDescription dfd){
		                        // for subscriptions
		                        dfd.clearAllServices(); //clear all services since we are deregistering
                            
                        }
            }
        }


        class SubscriptionBehaviour : SubscriptionInitiator
        {
            System.Collections.Hashtable wsStore = null;
            private string urlLocation = null;
            private string path = null;
            private AssemblySerializationBinder assemblyBinder = new AssemblySerializationBinder(AppDomain.CurrentDomain.GetAssemblies());

            public SubscriptionBehaviour(Agent a, ACLMessage msg, System.Collections.Hashtable wsStore, string urlLocation, string path)
                : base(a, msg)
            {
                ((SubscriptionAgent)this.myAgent).debug("Subscription behaviour started!");
                this.wsStore = wsStore;
                this.urlLocation = urlLocation;
                this.path = path;
            }

            /// <summary>
            /// Override method that should be able to handle inform messages(subscriptions)
            /// </summary>
            /// <param name="inform">ACLMessage includes notification about registering/deregistering of an agent</param>
            public override void handleInform(ACLMessage inform)
            {
                ((SubscriptionAgent)this.myAgent).debug("New notification from DF!");
                try
                {
                    
                    DFAgentDescription[] description = DFService.decodeNotification(inform.getContent());
                    foreach (DFAgentDescription desc in description)
                    {
                        jade.util.leap.Iterator iter = desc.getAllServices();
                        if (iter.hasNext())
                        {
                            ServiceDescription d = (ServiceDescription)iter.next();
                            // register agent in web service store
                            registerAgentAsWebService(d);
                        }
                        else
                        {
                            deregisterAgent(desc);
                        }
                        
                    }
                }
                catch (Exception e)
                {
                    ((SubscriptionAgent)this.myAgent).error(e);
                }
                finally
                {
                    //Monitor.Exit(wsStore);
                    //Monitor.PulseAll(wsStore);
                }
                
            }

            private void deregisterAgent(DFAgentDescription desc)
            {
               
            }
            /// <summary>
            /// Get credentials of Agent as a AgentName, Publicable_As_WS flag and store them into WS_STORE
            /// </summary>
            /// <param name="d">service description of a new agent</param>
            private void registerAgentAsWebService(ServiceDescription d)
            {
                AgentDescription agentDesc = new AgentDescription();
                System.Collections.ArrayList allowedOperations = new System.Collections.ArrayList();
                String assemblyNameGlobal = null;
                Iterator iter = d.getAllProperties();
                while (iter.hasNext())
                {
                    Property p = (Property)iter.next();
                    if (p.getName().Equals(GWConst.PUBLIC_AS_WEBSERVICE))
                    {
                        agentDesc.publicable = Boolean.Parse(p.getValue().ToString());
                    }
                    else if (p.getName().Equals(GWConst.OPERATION))
                    {
                        String value = (String)p.getValue();
                        // prepare values from operation string
                        String ontologyName = value.Split('.')[0];
                        String assemblyName = value.Split('.')[1];
                        String action = value.Split('.')[2];
                        //String soapAction = "http://" + assemblyName + "." + action;
                        String soapAction = assemblyName + "." + action;

                        // prepare operation description object
                        OperationDescription operation = new OperationDescription();
                        operation.assemblyName = assemblyName;
                        operation.ontology = (Ontology)Activator.CreateInstance(assemblyBinder.BindToType(null, ontologyName)); 
                        this.myAgent.getContentManager().registerOntology(operation.ontology);
                        operation.soapAction = soapAction;
                        operation.operationName = action;

                        allowedOperations.Add(action);
                        assemblyNameGlobal = assemblyName;
                        // add operation do agent description
                        agentDesc.operations.Add(soapAction, operation);
                    }
                }
                Iterator onto = d.getAllOntologies();
                this.wsStore.Add(d.getName(), agentDesc);


                if (agentDesc.publicable)
                {
                    // generate WSDL from ontology
                    //TODO: use right name of assembly for each operations instead of one global assembly name for all operations
                    generateWSDL(d, d.getName(), assemblyNameGlobal, allowedOperations);
                    updateWSIL(d.getName(), this.urlLocation);

                }
                
                log.Debug("Created new agent in WS Store: " + d.getName() + ", publicable=" + agentDesc.publicable);
            }

            /// <summary>
            /// update WS-Inspection file (wsil) located at /webroot/inspection.wsil
            /// </summary>
            /// <param name="agentName"></param>
            /// <param name="urlLocation"></param>
            private void updateWSIL(string agentName, string urlLocation)
            {
                WSILGenerator gen = new WSILGenerator(path + "inspection.wsil");
                gen.addWS(urlLocation + agentName + ".agent?wsdl", agentName);
                gen.saveWSIL();
            }

            

            private void generateWSDL(ServiceDescription d, string agentName, string assemblyName, System.Collections.ArrayList allowedOperations)
            {
                //throw new NotImplementedException();
                ContentManager manager = this.myAgent.getContentManager();
                
                Iterator ontologies=d.getAllOntologies();
                while (ontologies.hasNext())
                {
                    String ontoName = (String)ontologies.next();
                    Ontology ontology = (Ontology)Activator.CreateInstance(assemblyBinder.BindToType(null, ontoName));
                    this.myAgent.getContentManager().registerOntology(ontology);

                    IOntology2WSDL wsdlGenerator = new WSDLGenerator();
                    wsdlGenerator.setAllowedOperations(allowedOperations);
                    MemoryStream stream = wsdlGenerator.getWSDLStream(ontology, agentName, assemblyName, this.urlLocation);
                    FileStream outputStream = File.Create(path+agentName+".wsdl");
                    stream.WriteTo(outputStream);
                    stream.Close();
                    outputStream.Close();

                }
            }


        }


        /*
        /// <summary>
        /// Subscriber of AMS (Agent management service) events for registering and removing agents
        /// </summary>
        class AgentAMSSubscriber : AMSSubscriber
        {
            public string localeServerURL = null;
            public string localeServerPath = null;
            public Agent agent = null;
            Hashtable wsStore = null;
            public AgentAMSSubscriber(Agent agent, Hashtable wsStore, string localeURL, string path)
            {
                this.localeServerURL = localeURL;
                this.localeServerPath = path;
                this.wsStore = wsStore;
                this.agent = agent;
            }

            public override void installHandlers(java.util.Map handlersTable)
            {
                handlersTable.put("born-agent", new AgentRegisterEventHandler(ref this.wsStore, this.localeServerPath, this.localeServerURL, ref agent));
                handlersTable.put("dead-agent", new AgentDeadEventHandler(ref this.wsStore, ref agent));
            }

            internal class AgentRegisterEventHandler:EventHandler
            {
                Hashtable wsStore = null;
                string localeServerURL = null;
                string localeServerPath = null;
                Agent myAgent = null;
                private AssemblySerializationBinder assemblyBinder = new AssemblySerializationBinder(AppDomain.CurrentDomain.GetAssemblies());
                public AgentRegisterEventHandler(ref Hashtable wsStore, string localeServerPath, string localeServerURL, ref Agent myAgent)
                {
                    this.wsStore = wsStore;
                    this.localeServerPath = localeServerPath;
                    this.localeServerURL = localeServerURL;
                    this.myAgent = myAgent;
                }
                #region EventHandler Members

                public void handle(Event ev)
                {
                    BornAgent agent = (BornAgent)ev;
                    agent.getAgent();
                    DFAgentDescription dfd = new DFAgentDescription();
                    dfd.setName(agent.getAgent());
                    dfd.addServices(new ServiceDescription());

                    DFAgentDescription[] d = DFService.search(this.myAgent, dfd);
                    
                    
                    
                }

                /// <summary>
                /// Get credentials of Agent as a AgentName, Publicable_As_WS flag and store them into WS_STORE
                /// </summary>
                /// <param name="d">service description of a new agent</param>
                private void registerAgentAsWebService(ServiceDescription d)
                {
                    AgentDescription agentDesc = new AgentDescription();
                    System.Collections.ArrayList allowedOperations = new System.Collections.ArrayList();
                    String assemblyNameGlobal = null;
                    Iterator iter = d.getAllProperties();
                    while (iter.hasNext())
                    {
                        Property p = (Property)iter.next();
                        if (p.getName().Equals(GWConst.PUBLIC_AS_WEBSERVICE))
                        {
                            agentDesc.publicable = Boolean.Parse(p.getValue().ToString());
                        }
                        else if (p.getName().Equals(GWConst.OPERATION))
                        {
                            String value = (String)p.getValue();
                            // prepare values from operation string
                            String ontologyName = value.Split('.')[0];
                            String assemblyName = value.Split('.')[1];
                            String action = value.Split('.')[2];
                            //String soapAction = "http://" + assemblyName + "." + action;
                            String soapAction = assemblyName + "." + action;

                            // prepare operation description object
                            OperationDescription operation = new OperationDescription();
                            operation.assemblyName = assemblyName;
                            operation.ontology = (Ontology)Activator.CreateInstance(assemblyBinder.BindToType(null, ontologyName));
                            this.myAgent.getContentManager().registerOntology(operation.ontology);
                            operation.soapAction = soapAction;
                            operation.operationName = action;

                            allowedOperations.Add(action);
                            assemblyNameGlobal = assemblyName;
                            // add operation do agent description
                            agentDesc.operations.Add(soapAction, operation);
                        }
                    }
                    Iterator onto = d.getAllOntologies();
                    this.wsStore.Add(d.getName(), agentDesc);


                    if (agentDesc.publicable)
                    {
                        // generate WSDL from ontology
                        //TODO: use right name of assembly for each operations instead of one global assembly name for all operations
                        generateWSDL(d, d.getName(), assemblyNameGlobal, allowedOperations);
                    }

                    log.Debug("Created new agent in WS Store: " + d.getName() + ", publicable=" + agentDesc.publicable);
                }



                private void generateWSDL(ServiceDescription d, string agentName, string assemblyName, System.Collections.ArrayList allowedOperations)
                {
                    //throw new NotImplementedException();
                    ContentManager manager = this.myAgent.getContentManager();

                    Iterator ontologies = d.getAllOntologies();
                    while (ontologies.hasNext())
                    {
                        String ontoName = (String)ontologies.next();
                        Ontology ontology = (Ontology)Activator.CreateInstance(assemblyBinder.BindToType(null, ontoName));
                        this.myAgent.getContentManager().registerOntology(ontology);

                        IOntology2WSDL wsdlGenerator = new WSDLGenerator();
                        wsdlGenerator.setAllowedOperations(allowedOperations);
                        MemoryStream stream = wsdlGenerator.getWSDLStream(ontology, agentName, assemblyName, this.localeServerURL);
                        FileStream outputStream = File.Create(this.localeServerPath + "/" + agentName + ".wsdl");
                        stream.WriteTo(outputStream);
                        stream.Close();
                        outputStream.Close();

                    }
                }

                #endregion
            }

            internal class AgentDeadEventHandler : EventHandler
            {
                Hashtable wsStore = null;
                
                Agent myAgent = null;
                public AgentDeadEventHandler(ref Hashtable wsStore,ref  Agent myAgent)
                {
                    this.wsStore = wsStore;
                    this.myAgent = myAgent;
                }
                #region EventHandler Members

                public void handle(Event ev)
                {
                    DeadAgent da = (DeadAgent)ev;
                    AID id = da.getAgent();
                    // Deregister the dead agent in case it was registered
                    try
                    {
                        DFAgentDescription dfd = new DFAgentDescription();
                        dfd.setName(id);
                        //DFService.deregister(da.getAgent());
                        SubscriptionAgent.wsStore.Remove(id.getName().Split('@')[0]);

                    }
                    catch (Exception e)
                    {
                        // Just do nothing
                    }
                }


                #endregion
            }

        }
        */

   }


}
